#include <GL/glew.h>
#include <GL/glut.h>
#include <iostream>
#include <fstream>

#define RENDER_SPHERES_INSTEAD_OF_VERTICES 0

GLhandleARB vert_hdlr, frag_hdlr, prog_hdlr;
GLint location_attribute_0,location_ogf_uniform_0;
float light0_position[4] = {1,1,1,0};
const float camera[] = {0.0,0.0,5.0};

float randMinusOneOne() {
	return (float)rand()/(float)RAND_MAX*(rand()>RAND_MAX/2?1:-1);
}

void render_scene(void) {
	srand (0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	gluLookAt(camera[0], camera[1], camera[2], 0.0,  0.0, 0.0, 0.0f, 1.0f, 0.0f);

#if !RENDER_SPHERES_INSTEAD_OF_VERTICES
	GLfloat viewport[4];
	glGetFloatv(GL_VIEWPORT, viewport);
	glUniform4fvARB(location_ogf_uniform_0, 1, viewport);
	glBegin(GL_POINTS);
#endif
	for (int i=0; i<50; i++) {
		float x = randMinusOneOne();
		float y = randMinusOneOne();
		float z = randMinusOneOne();
		float radius = (randMinusOneOne()+2.0)/8.0;
		float r = (float)rand()/(float)RAND_MAX;
		float g = (float)rand()/(float)RAND_MAX;
		float b = (float)rand()/(float)RAND_MAX;
		glColor3f(r,g,b);
#if RENDER_SPHERES_INSTEAD_OF_VERTICES
		glPushMatrix();
		glTranslatef(x, y, z);
		glutSolidSphere (radius, 32, 32);
		glPopMatrix();
#else
		glVertexAttrib1fARB(location_attribute_0, radius);
		glVertex3f(x, y, z);
#endif
	}
#if !RENDER_SPHERES_INSTEAD_OF_VERTICES
		glEnd();
#endif
	glutSwapBuffers();
}

void processNormalKeys(unsigned char key, int x, int y) {
	if (27==key) {
		exit(0);
	}
}

void printInfoLog(GLhandleARB obj) {
	int log_size = 0;
	int bytes_written = 0;
	glGetObjectParameterivARB(obj, GL_OBJECT_INFO_LOG_LENGTH_ARB, &log_size);
	if (!log_size) return;
	char *infoLog = new char[log_size];
	glGetInfoLogARB(obj, log_size, &bytes_written, infoLog);
	std::cerr << infoLog << std::endl;
	delete [] infoLog;
}

void changeSize(int w, int h) {
	float ratio = (1.0*w)/(!h?1:h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
    glViewport(0, 0, w, h);
//	gluPerspective(45,ratio,1,100);
	glOrtho(-2,2,-2,2,1,8);
	glMatrixMode(GL_MODELVIEW);

}


bool read_n_compile_shader(const char *filename, GLhandleARB &hdlr, GLenum shaderType) {
	std::ifstream is(filename, std::ios::in|std::ios::binary|std::ios::ate);
	if (!is.is_open()) {
		std::cerr << "Unable to open file " << filename << std::endl;
		return false;
	}
	long size = is.tellg();
	char *buffer = new char[size+1];
	is.seekg(0, std::ios::beg);
	is.read (buffer, size);
	is.close();
	buffer[size] = 0;

	hdlr = glCreateShaderObjectARB(shaderType);
	glShaderSourceARB(hdlr, 1, (const GLcharARB**)&buffer, NULL);
	glCompileShaderARB(hdlr);
	std::cerr << "infolog for " << filename << std::endl;
	printInfoLog(hdlr);
	delete [] buffer;
	return true;
}

void setShaders() {
	read_n_compile_shader("vertex_shader.gpu", vert_hdlr, GL_VERTEX_SHADER_ARB);
	read_n_compile_shader("fragment_shader.gpu", frag_hdlr, GL_FRAGMENT_SHADER_ARB);

	prog_hdlr = glCreateProgramObjectARB();
	glAttachObjectARB(prog_hdlr, vert_hdlr);
	glAttachObjectARB(prog_hdlr, frag_hdlr);
	glLinkProgramARB(prog_hdlr);
	std::cerr << "info log for the sphere shaders: " << std::endl;
	printInfoLog(prog_hdlr);
	location_attribute_0   = glGetAttribLocationARB(prog_hdlr, "attr");	// radius
	location_ogf_uniform_0 = glGetUniformLocationARB(prog_hdlr, "ogf_uniform_0"); // viewport

	glUseProgramObjectARB(prog_hdlr);
}

int main(int argc, char **argv) {
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowPosition(100,100);
	glutInitWindowSize(800,800);
	glutCreateWindow("");

	glutDisplayFunc(render_scene);
	glutReshapeFunc(changeSize);
	glutKeyboardFunc(processNormalKeys);

	glEnable(GL_COLOR_MATERIAL);
	glEnable(GL_DEPTH_TEST);
	glClearColor(0.0,0.0,1.0,1.0);

	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glLightfv(GL_LIGHT0, GL_POSITION, light0_position);

#if (!RENDER_SPHERES_INSTEAD_OF_VERTICES)
	glewInit();
	if (GLEW_ARB_vertex_shader && GLEW_ARB_fragment_shader)
		std::cout << "Ready for GLSL" << std::endl;
	else {
		std::cout << "No GLSL support" << std::endl;
		exit(1);
	}
	setShaders();
	glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
#endif
	glutMainLoop();
	return 0;
}

